package com.gframework.mybatis.dao.mybatis.provider.per;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

import javax.persistence.Id;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.builder.annotation.ProviderContext;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.reflection.MetaObject;

import com.gframework.mybatis.config.MybatisConfig;
import com.gframework.mybatis.dao.IPrimaryDAO;
import com.gframework.mybatis.dao.mybatis.provider.MybatisDaoInfo;
import com.gframework.mybatis.dao.mybatis.provider.PojoInfo;
import com.gframework.mybatis.dao.mybatis.provider.ProviderSQLUtils;
import com.gframework.mybatis.dao.mybatis.provider.core.ProviderPlusLanguageDriver;
import com.gframework.sqlparam.MybatisParamNameCacheGenerator;
import com.gframework.sqlparam.NameGenerator;

/**
 * {@link IPrimaryDAO} 接口的sql提供类.<br>
 * 
 * @since 1.0.0
 * @author Ghwolf
 * 
 * @see IPrimaryDAO
 * @see ProviderPlusLanguageDriver
 */
public class PrimaryDaoProvider extends AbstractProvider{
	
	/**
	 * 根据主键查询一条数据.
	 * @see IPrimaryDAO#findById(Serializable)
	 */
	public static String findById(ProviderContext context) {
		MybatisDaoInfo mybatisDao = getMybatisDaoInfo(context);
		PojoInfo pojo = mybatisDao.getPojoInfo();
		return new SQL()
				.SELECT(pojo.getColumNames())
				.FROM(pojo.getTableName())
				.WHERE(pojo.getPkColumnName() + "=#{_parameter}")
				.toString();
	}
	
	/**
	 * 根据主键和额外一个条件查询一条数据. 用and连接。
	 * @see IPrimaryDAO#findByIdCondition(Serializable, com.gframework.sqlparam.Param)
	 */
	public static String findByIdCondition(@Param("id") Serializable id,com.gframework.sqlparam.Param p, ProviderContext context) {
		return findByIdConditions(id,p,null,context);
	}
	
	/**
	 * 根据主键和额外两个条件查询一条数据. 用and连接。
	 * @see IPrimaryDAO#findByIdConditions(Serializable, com.gframework.sqlparam.Param, com.gframework.sqlparam.Param)
	 */
	public static String findByIdConditions(@Param("id") Serializable id, com.gframework.sqlparam.Param p1,
			com.gframework.sqlparam.Param p2, ProviderContext context) {
		MybatisDaoInfo mybatisDao = getMybatisDaoInfo(context);
		PojoInfo pojo = mybatisDao.getPojoInfo();
		SQL sql = new SQL()
				.SELECT(pojo.getColumNames())
				.FROM(pojo.getTableName())
				.WHERE(pojo.getPkColumnName() + "=#{id}");

		Map<String,Object> param = setExtParam(sql, p1, p2);
		if (!param.isEmpty()) {
			ProviderPlusLanguageDriver.setParam(param);
		}
		
		return sql.toString() ;
	}
	
	/**
	 * 根据主键批量查询数据.
	 * @see IPrimaryDAO#findByIds(java.util.Collection)
	 */
	public static String findByIds(@Param("ids") Collection<? extends Serializable> ids,ProviderContext context) {
		return findByIds0(ids,context,null);
	}
	
	/**
	 * 根据主键批量查询数据.
	 */
	private static String findByIds0(@Param("ids") Collection<? extends Serializable> ids,ProviderContext context,BiConsumer<SQL, Map<String,Object>> consumer) {
		MybatisDaoInfo mybatisDao = getMybatisDaoInfo(context);
		PojoInfo pojo = mybatisDao.getPojoInfo();
		
		Map<String,Object> param = new HashMap<>(ids.size());
		SQL sql = new SQL()
				.SELECT(pojo.getColumNames())
				.FROM(pojo.getTableName())
				.WHERE(pojo.getPkColumnName() + " IN " + ProviderSQLUtils.foreach(ids, "(", ")", ",", "id", param));
		if (consumer != null) {
			consumer.accept(sql, param);
		}
		if (!param.isEmpty()) {
			ProviderPlusLanguageDriver.setParam(param);
		}
		return sql.toString() ;
	}
	
	/**
	 * 根据主键和额外一个条件批量查询数据.用and连接。
	 * @see IPrimaryDAO#findByIdsCondition(Collection, com.gframework.sqlparam.Param)
	 */
	public static String findByIdsCondition(@Param("ids") Collection<? extends Serializable> ids,
			com.gframework.sqlparam.Param p, ProviderContext context) {
		return findByIds0(ids, context, (sql, param) -> setExtParam(sql, p, null, param));
	}
	/**
	 * 根据主键和额外两个条件批量查询数据.用and连接。
	 * @see IPrimaryDAO#findByIdsConditions(Collection, com.gframework.sqlparam.Param, com.gframework.sqlparam.Param)
	 */
	public static String findByIdsConditions(@Param("ids") Collection<? extends Serializable> ids,
			com.gframework.sqlparam.Param p1, com.gframework.sqlparam.Param p2, ProviderContext context) {
		return findByIds0(ids,context,(sql,param) -> setExtParam(sql, p1, p2, param));
	}
	
	/**
	 * 根据主键删除一条数据.
	 * @see IPrimaryDAO#deleteById(Serializable)
	 */
	public static String deleteById(ProviderContext context) {
		MybatisDaoInfo mybatisDao = getMybatisDaoInfo(context);
		PojoInfo pojo = mybatisDao.getPojoInfo();
		return new SQL()
				.DELETE_FROM(pojo.getTableName())
				.WHERE(pojo.getPkColumnName() + "=#{_parameter}")
				.toString();
	}
	
	/**
	 * 根据主键以及额外一个条件删除一条数据. 使用 and 连接。
	 * @see IPrimaryDAO#deleteByIdCondition(Serializable, com.gframework.sqlparam.Param)
	 */
	public static String deleteByIdCondition(@Param("id") Serializable id,com.gframework.sqlparam.Param p, ProviderContext context) {
		return deleteByIdConditions(id,p,null,context);
	}
	
	/**
	 * 根据主键以及额外两个条件删除一条数据. 使用 and 连接。
	 * @see IPrimaryDAO#deleteByIdConditions(Serializable, com.gframework.sqlparam.Param, com.gframework.sqlparam.Param)
	 */
	public static String deleteByIdConditions(@Param("id") Serializable id, com.gframework.sqlparam.Param p1,
			com.gframework.sqlparam.Param p2, ProviderContext context) {
		MybatisDaoInfo mybatisDao = getMybatisDaoInfo(context);
		PojoInfo pojo = mybatisDao.getPojoInfo();
		SQL sql = new SQL()
				.DELETE_FROM(pojo.getTableName())
				.WHERE(pojo.getPkColumnName() + "=#{id}");
		Map<String,Object> param = setExtParam(sql, p1, p2);
		if (!param.isEmpty()) {
			ProviderPlusLanguageDriver.setParam(param);
		}
		return sql.toString();
	}
	
	/**
	 * 根据主键批量删除数据.
	 * @see IPrimaryDAO#deleteByIds(Collection)
	 */
	public static String deleteByIds(@Param("ids") Collection<? extends Serializable> ids, ProviderContext context) {
		return deleteByIds0(ids,context,null);
	}
	/**
	 * 根据主键批量删除数据.
	 */
	private static String deleteByIds0(@Param("ids") Collection<? extends Serializable> ids, ProviderContext context,BiConsumer<SQL,Map<String,Object>> consumer) {
		MybatisDaoInfo mybatisDao = getMybatisDaoInfo(context);
		PojoInfo pojo = mybatisDao.getPojoInfo();
		
		Map<String,Object> param = new HashMap<>(ids.size() + 2);
		SQL sql = new SQL()
				.DELETE_FROM(pojo.getTableName())
				.WHERE(pojo.getPkColumnName() + " IN " + ProviderSQLUtils.foreach(ids, "(", ")", ",", "id", param));
		if (consumer != null) {
			consumer.accept(sql, param);
		}
		if (!param.isEmpty()) {
			ProviderPlusLanguageDriver.setParam(param);
		}
		return sql.toString() ;
	}
	
	/**
	 * 根据主键及额外一个条件批量删除数据. 使用and连接
	 * @see IPrimaryDAO#deleteByIdsCondition(Collection, com.gframework.sqlparam.Param)
	 */
	public static String deleteByIdsCondition(@Param("ids") Collection<? extends Serializable> ids,
			com.gframework.sqlparam.Param p, ProviderContext context) {
		return deleteByIds0(ids,context,(sql,param) -> setExtParam(sql,p,null,param));
	}
	
	/**
	 * 根据主键及额外两个条件批量删除数据. 使用and连接
	 * @see IPrimaryDAO#deleteByIdsConditions(Collection, com.gframework.sqlparam.Param, com.gframework.sqlparam.Param)
	 */
	public static String deleteByIdsConditions(@Param("ids") Collection<? extends Serializable> ids,
			com.gframework.sqlparam.Param p1, com.gframework.sqlparam.Param p2, ProviderContext context) {
		return deleteByIds0(ids,context,(sql,param) -> setExtParam(sql,p1,p2,param));
	}
	
	/**
	 * 根据主键更新一条数据的非null字段.
	 * @see IPrimaryDAO#updateById(Serializable)
	 */
	public static String updateById(Serializable vo, ProviderContext context) {
		return updateById0(vo,context,true);
	}
	
	/**
	 * 根据主键更新一条数据的非null字段.
	 * @see IPrimaryDAO#updateAllColumnById(Serializable)
	 */
	public static String updateAllColumnById(Serializable vo, ProviderContext context) {
		return updateById0(vo,context,false);
	}
	
	/**
	 * 根据id更新
	 */
	private static String updateById0(Serializable vo, ProviderContext context,boolean ignoreNullValue) {
		MybatisDaoInfo mybatisDao = getMybatisDaoInfo(context);
		PojoInfo pojo = mybatisDao.getPojoInfo();
		MetaObject m = MybatisConfig.newMetaObject(vo);
		
		String[] fs = pojo.getFieldNames();
		String[] cs = pojo.getColumNames();
		Map<String,Object> param = new HashMap<>(fs.length + 1);
		
		SQL sql = new SQL().UPDATE(pojo.getTableName());
		
		// set语句
		Object pkValue = null;
		for (int x = 0 ; x < fs.length ; x ++) {
			String f = fs[x];
			String c = cs[x];
			if (m.hasGetter(f)) {
				Object value = m.getValue(f);
				if (!ignoreNullValue || value != null) {
					sql.SET(c + " = #{" + f + "}");
					param.put(f,value);
				}
				if (c.equals(pojo.getPkColumnName())) {
					if (value == null) {
						throw new PrimaryKeyNotFoundException("执行updateById操作时，没有指定主键！");
					} else {
						pkValue = value ;
					}
				}
			} else {
				// 逻辑上不会出现这个错误
				throw new BindingException(vo.getClass() + " 类没有 " + f + " 成员属性或getter方法，无法绑定sql参数到 " + f + " 列上！");
			}
		}
		
		sql.WHERE(pojo.getPkColumnName() + "=#{!id}");
		// pkValue一定不是null，否则在最开始就会出现异常
		param.put("!id", pkValue);

		ProviderPlusLanguageDriver.setParam(param);
		return sql.toString();
	}
	
	/**
	 * 设置额外两个扩展参数的sql条件，返回一个新的包含了新参数的map集合
	 */
	private static Map<String,Object> setExtParam(SQL sql,com.gframework.sqlparam.Param p1,com.gframework.sqlparam.Param p2) {
		Map<String,Object> param = new HashMap<>();
		setExtParam(sql,p1,p2,param);
		return param ;
	}
	/**
	 * 设置额外两个扩展参数的sql条件
	 */
	private static void setExtParam(SQL sql,com.gframework.sqlparam.Param p1,com.gframework.sqlparam.Param p2,Map<String,Object> param) {
		NameGenerator ng = new MybatisParamNameCacheGenerator();
		String w = p1 == null ? null : p1.getSql(param,ng);
		if (w != null) {
			sql.WHERE(w);
		}
		w = p2 == null ? null : p2.getSql(param,ng);
		if (w != null) {
			sql.WHERE(w);
		}
	}
	
	/**
	 * 取得MybatisDaoInfo类对象，并验证参数.
	 */
	protected static MybatisDaoInfo getMybatisDaoInfo(ProviderContext context) {
		MybatisDaoInfo mybatisDao = AbstractProvider.getMybatisDaoInfo(context);
		if (mybatisDao.getPojoInfo().getPkColumnName() == null) {
			throw new PrimaryKeyNotFoundException(
					context.getMapperType().getName() + " POJO类未指定主键，请通过 @" + Id.class.getName() + " 指定唯一的一个主键。");
		}
		return mybatisDao ;
	} 
	
}

